package relu.fbs.jfm;

import java.util.*;

import jfm.model.Types.CropType;
import relu.fbs.*;
import relu.fbs.xt.FBSCropData.FBSCDatum;
import relu.fbs.jfm.AbstractJFMCropData.JFMCDatum;
import relu.fbs.xt.FBSFarmData.FBSFarmDatum;
import relu.fbs.xt.*;

public class JFMFarmData extends AbstractJFMFarmData {


	
	private enum Check {
		AREACROPPEDSA, // +tve means not all cropped sa accounted for
		AREASA, // THE area of set aside
		MODELABLEAREA, // The total area of identified crops 
		AREASUM1, // 
		AREASUM2,
		SETASIDESUBSIDY, // Value of set aside subsidy payments per hectare.
		UNMODELLABLERATIO; // proportion of crops not modellable in the jfm
	}

	/** Subject farms to a series of checks and return true only if all pass */
	boolean passAllChecks(FBSFarmData fbsd){
		if ( checkValues.get(Check.UNMODELLABLERATIO) > 0.2 ){
			return false;
		}/*
		if ( checkValues.get(Check.SETASIDESUBSIDY) < 215 || checkValues.get(Check.SETASIDESUBSIDY) > 260){
			return false;
		}*/
		/*
		if ( checkValues.get(Check.AREASA) == 0.0 ){
			return false; // Must have some set aside 
		}
		*/
		// This checks that the modelable+unmodellable (ie identified) area equals utilizedArea (+- epsilon%)
		if ( checkValues.get(Check.AREASUM1) > 0.02){
//			throw new Error("Area sum 1 for "+fbsd.farmId);
			return false;
		}
		if ( checkValues.get(Check.AREASUM2) > 0.03){
//			throw new Error("Area sum 2 for "+fbsd.farmId);
//			return false;
		}
		if ( checkValues.get(Check.MODELABLEAREA) < 100 ){
			return false;
		}
		if ( fbsd.isBad()){
			return false;
		}
		if ( fbsd.hasBadCrops()){
			return false;
		}
		return true;
	}
	
	private final Map<Check,Double> checkValues = new HashMap<Check,Double>();
	
	// ---- ROUTINES FOR CALCULATING VALUES DIRECT FROM FBS DATA ----- //
	/** @return the total area of uncropped land including setaside
	 * 
	 * 
	 * For now this just returns a sum of all the different types of set aside.  Actually these only overlap for some years. 
	 * The following overlaps and years are known. 
	 * 1993: 2,1 ( ?Summation appropriate ?)
	 * 1994-6: 3,1 ( Summation appropriate)
	 * 2005:1,0  ( Summation certainly appropriate)
	 * 
	 * */
	private double areaSA(FBSFarmData fbsd){
		double[] savals=new double[4];
		savals[0]=fbsd.getFarmDatum(FBSFarmDatum.SETASIDEAREA);
//		savals[1]=fbsd.getFarmDatum(FBSFarmDatum.UCAREA); // This includes all uncropped except setaside .. eg buildings etc ?? I think
		savals[1]=fbsd.getFarmDatum(FBSFarmDatum.SYRSETASIDEAREA);
		savals[2]=fbsd.getFarmDatum(FBSFarmDatum.SYSETASIDEAREA);
		savals[3]=fbsd.getFarmDatum(FBSFarmDatum.CSETASIDEAREA);
		boolean foundVal=false;
//		int originali=0;
		double sa=0;
		for ( int i=0;i<4;i++){
			sa+=savals[i];
			/*
			if ( savals[i] > 0 ){
				if ( foundVal){
//					System.out.println("Conflicting SA Types new is "+i+" "+savals[i]+" old is "+originali+" "+savals[originali]);
//					throw new Error("Conflicting SA Types new is "+i+" "+savals[i]+" old is "+originali+" "+savals[originali]);
				}
				originali=i;
				foundVal=true;
			}
			*/
		}
		checkValues.put(Check.AREASA, sa);
		return sa;
	}
	/** @return The total area of set-aside that is cropped */
	private double areaCroppedSA(FBSFarmData fbsd) {
		double csaAll=0; // Amount calculated using all crops 
		double csaJFM = 0 ; // Amount calculated using jfm crops only
		for ( CropType ct :  FarmData.fbsCropsKeySet){
			double sa=fbsd.getCrop(ct).getValue(FBSCDatum.AREASA);
			csaAll+=sa;
			if ( FarmData.mapToJFMCrop.containsKey(ct)){
				csaJFM+=sa;
			}
		}
		checkValues.put(Check.AREACROPPEDSA,(csaAll-csaJFM));
		return csaJFM;
	}
	private enum AreaType {
		COMBINABLE,NEGLIST,SETASIDE,UC,
		MODELABLE,UNMODELABLE,
		TOTALIDENTIFIED;		
	}
	
	/** @return breakdown of farmed area into 
	 * 
	 * - 0 Combinable area
	 * - 1 negative list crop area
	 * - 2 sum of negative list and combinable
	 * - 3 total area of identified crops 
	 * 
	 * */
	private Map<AreaType,Double> areaBreakdown(FBSFarmData fbsd){
		Map<AreaType,Double> areas=new HashMap<AreaType,Double>();
		for( AreaType at:AreaType.values()){
			areas.put(at, 0.0);
		}

		double totalAreaIdentified=0;
		for(CropType ct:FarmData.fbsCropsKeySet){
			double ca=fbsd.getCrop(ct).getValue(FBSCDatum.AREA);
			ca+=fbsd.getCrop(ct).getValue(FBSCDatum.AREASA);
			totalAreaIdentified+=ca;
		}
		
		double jfmIdentified=0;
		double combinableArea=0;
		double negListArea=0;
		double unModelledArea=0;
		double unCroppedSetAsideArea=this.areaSA(fbsd);
		double unCroppedAreaNotSA=fbsd.getFarmDatum(FBSFarmDatum.UCAREA); // Perhaps should also add other forms of uncropped area
		HashSet<CropType> allJFMCrops = new HashSet<CropType>();
		allJFMCrops.addAll(jfmCropTypesMap.keySet());
		allJFMCrops.addAll(jfmSetAsideCrops.values());
		for(CropType ct:allJFMCrops){
			if ( !crops.containsKey(ct)){
				throw new Error("No crop "+ct);
			}
			double ca=crops.get(ct).getValue(JFMCDatum.AREA);
			jfmIdentified+=ca;
			if ( FarmData.jfmCombinableCrops.contains(ct)){ // Includes Crops on set aside that are modellable and combinable 
				combinableArea+=ca;
			} else if ( FarmData.jfmNegativeListCrops.contains(ct)){
				negListArea+=ca;
			} else if ( FarmData.jfmUnModelledCrops.contains(ct)){ // Includes unmodelled crops on set aside 
				unModelledArea+=ca;
			} else {
				throw new Error("No mapping to jfm crop for "+ct);
			}
		}
		double adiff=totalAreaIdentified - jfmIdentified;
		if ( Math.abs(adiff) > 0.1 ){
			throw new Error("The jfm crops list identified "+jfmIdentified+" but total identified is "+totalAreaIdentified);
		}
		areas.put(AreaType.COMBINABLE, combinableArea);
		areas.put(AreaType.NEGLIST, negListArea);
		areas.put(AreaType.SETASIDE, unCroppedSetAsideArea);
		areas.put(AreaType.TOTALIDENTIFIED, totalAreaIdentified);
		double modelableArea=combinableArea+negListArea+unCroppedSetAsideArea;
		areas.put(AreaType.MODELABLE,modelableArea);
		areas.put(AreaType.UNMODELABLE, unModelledArea+unCroppedAreaNotSA);
		areas.put(AreaType.UC, unCroppedAreaNotSA);
		double utA = fbsd.getFarmDatum(FBSFarmDatum.UTILIZEDAREA);
		double asum1=1;
		double asum2=1;
		
		double unmodellableRatio=1;
		if ( utA > 0 ){
			unmodellableRatio=unModelledArea/utA;
			asum1=Math.abs((utA-modelableArea-unModelledArea)/utA);
			asum2=Math.abs(utA-totalAreaIdentified/utA);
		}
		checkValues.put(Check.AREASUM1,asum1);
		checkValues.put(Check.AREASUM2, asum2);
		checkValues.put(Check.UNMODELLABLERATIO, unmodellableRatio);
		checkValues.put(Check.MODELABLEAREA, areas.get(AreaType.MODELABLE));
		
		return areas;
	}
	
	private double setAsideSubsidy(FBSFarmData fbsd){
		double area=fbsd.getFarmDatum(FBSFarmDatum.SETASIDEAREA);
		double apay=fbsd.getFarmDatum(FBSFarmDatum.SETASIDEPAYMENT);
		double aeo=fbsd.getFarmDatum(FBSFarmDatum.SETASIDEEO);
		double hapay=0;
		if ( area <= 0 ){
			checkValues.put(Check.SETASIDESUBSIDY, 0.0);
			return 0;
		}
		if ( apay <= 0 ){
			if ( aeo <= 0 ){
				System.out.println(" No way to calculate set aside subsidy ");
				checkValues.put(Check.SETASIDESUBSIDY, 0.0);
				return 0;
			} else {
				checkValues.put(Check.SETASIDESUBSIDY, aeo/area);
				return aeo/area;
			}
		} else {
			checkValues.put(Check.SETASIDESUBSIDY,apay/area);
			return apay/area;
		}
		
	}
	
	
	public enum JFMFarmDatum {
		FARMID ("farmid",true),
		REGION ("region",true),
		COUNTY ("county",true),
		ALTITUDE ("altitude",true),

		/** Total area of setaside under crops */
		AREACROPPEDSA("totalCropsInSetAside"),
		/** Total area of uncropped set aside */
		AREASA("areasa"),
		/** Area uncropped not including set aside */
		UCAREA("ucarea"),
		/** Subsidy Value of Set Aside Per Hectare */
		SUBSIDYSA("subsidysa"),
		/** Total area that is fully modellable  */
		MODELABLEAREA("modelablearea"),
		/** Area that is unmodelable*/
		UNMODELABLEAREA("unmodelablearea"),
		/** Total Area .. should roughly equal sum of modelable and unmodelable areas */
		AREA ("area");
		
		

		public final String RName;
		/** If true then the datum is not subject to time averaging */
		public final boolean isStatic;
		private JFMFarmDatum(String rName_,boolean iS){
			RName=rName_;
			isStatic=iS;
		}

		private JFMFarmDatum(String rName_){
			RName=rName_;
			isStatic=false;
		}
	}
	
	/** Create a partly populated JFMFarmData object */
	private JFMFarmData(int id){
		super(id);
		farmData.put(JFMFarmDatum.FARMID, new DataValue((double)farmId));
		for(CropType ct:FarmData.jfmCropTypesMap.keySet()){
			crops.put(ct, new JFMCropData(ct));
		}
		
		// Add in the setaside versions for certain crops 
		for(CropType ct:FarmData.jfmSetAsideCrops.values()){
			crops.put(ct, new JFMCropData(ct));
		}
		crops.put(CropType.SETASIDE, new JFMCropData(CropType.SETASIDE));
	}
	
	public static JFMFarmData fromCSVLine(String line){
		String[] sdata=line.split(",");
		JFMFarmData fdata=null;
		int i=0;
		try {
			fdata = new JFMFarmData((int)Double.parseDouble(sdata[0]));
			i++;
			for ( JFMFarmDatum nm:JFMFarmDatum.values()){
				if ( nm != JFMFarmDatum.FARMID){
					fdata.farmData.put(nm, new DataValue(Double.parseDouble(sdata[i])));
					i++;
				}
			}
			for ( CropType ct:FarmData.jfmCropTypesMap.keySet()){
				i=fdata.crops.get(ct).addDataFromCSV(sdata,i);
			}
			for ( CropType ct:FarmData.jfmSetAsideCrops.values()){
				i=fdata.crops.get(ct).addDataFromCSV(sdata,i);
			}			
			i=fdata.crops.get(CropType.SETASIDE).addDataFromCSV(sdata,i);
		} catch (NumberFormatException ex){
			throw new Error(ex.getMessage()+" \n Could not create JFMFarmData from line "+line);
		}
		
		return fdata;
	}
	
	/** Create a JFMFarmData object from an FBSFarmData object */
	static JFMFarmData fromFBSFarmData(FBSFarmData fbsd) throws BadDataException {

		JFMFarmData jfmd=new JFMFarmData(fbsd.farmId);
		// Do simple one for one translations 
		jfmd.farmData.put(JFMFarmDatum.REGION, new DataValue(fbsd.getFarmDatum(FBSFarmDatum.REGION)));
		jfmd.farmData.put(JFMFarmDatum.COUNTY, new DataValue(fbsd.getFarmDatum(FBSFarmDatum.COUNTY)));
		jfmd.farmData.put(JFMFarmDatum.ALTITUDE, new DataValue(fbsd.getFarmDatum(FBSFarmDatum.ALTITUDE)));

		
		// Now translate the crops 
		for(CropType fbsct : FarmData.fbsCropsKeySet){
			CropType jfmct=FarmData.mapToJFMCrop.get(fbsct);
			if ( jfmct != null ){
//				System.out.println(jfmct);
				JFMCropData[] both=JFMCropData.fromFBSCropData(fbsd.getCrop(fbsct));
		//		System.out.println("Main "+both[0].cropType);
				if ( both[0]!=null){
					if ( !jfmd.crops.containsKey(jfmct)){
						throw new Error("new JFMFarmData object lacks crop "+jfmct+" for fbscrop "+fbsct);
					}
					JFMCropData cpdata=(JFMCropData)jfmd.crops.get(jfmct);
					cpdata.increment(both[0]); // This is guaranteed to exist from the constructor
					if ( both[1]!=null){
			//			System.out.println("Seta "+both[1].cropType);
						if ( !jfmd.crops.containsKey((both[1].cropType))){
							throw new Error("No set aside CropType defined for "+both[1].cropType);
						}
						cpdata=(JFMCropData)jfmd.crops.get(both[1].cropType);
						cpdata.increment(both[1]);
					}
				} else {
				// was a bad crop .. ignore for now
				}
			} else {
				// No jfm equivalent for this crop .. ignore it
			}
		}
		
		// Do Derived Quantities
		jfmd.farmData.put(JFMFarmDatum.AREACROPPEDSA, new DataValue(jfmd.areaCroppedSA(fbsd)));
		Map<AreaType,Double> areas = jfmd.areaBreakdown(fbsd);
		jfmd.farmData.put(JFMFarmDatum.AREASA, new DataValue(areas.get(AreaType.SETASIDE)));
		jfmd.farmData.put(JFMFarmDatum.UCAREA, new DataValue(areas.get(AreaType.UC)));
		jfmd.farmData.put(JFMFarmDatum.MODELABLEAREA, new DataValue(areas.get(AreaType.MODELABLE)));
		jfmd.farmData.put(JFMFarmDatum.UNMODELABLEAREA, new DataValue(areas.get(AreaType.UNMODELABLE)));
		jfmd.farmData.put(JFMFarmDatum.AREA, new DataValue(fbsd.getFarmDatum(FBSFarmDatum.UTILIZEDAREA)));
		jfmd.farmData.put(JFMFarmDatum.SUBSIDYSA, new DataValue(jfmd.setAsideSubsidy(fbsd)));
		
		

		// And Now put in the data for set aside which is now treated as a poper crop
		JFMCropData sacropdata=(JFMCropData)jfmd.crops.get(CropType.SETASIDE);
		sacropdata.addDatum(JFMCDatum.AREA, jfmd.farmData.get(JFMFarmDatum.AREASA).autoValue());
		
		return jfmd;
	}
	

}
